// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_skeleton_data_loader.h"
#include "mk_error.h"
#include "mk_bone.h"
#include "mk_memory_block.h"
#include "mk_engine.h"
#include "mk_file_system.h"
#include "mk_skeleton_data.h"

namespace MK3DE
{
	BOOL CSkeletonDataLoader::LoadSkeletonData(const BYTE* buffer, DWORD size, SkeletonData& data)
	{
		BOOL ret = FALSE;
		int result = 1;
		TiXmlDocument* pXML = NULL;
		TiXmlElement* node = NULL;
		int BoneNum = 0;
		int FrameNum = 0;
		BOOL Relative = TRUE;
		const char* name = NULL;

		pXML = new TiXmlDocument();
		MK_PROCESS_ERROR(pXML);

		pXML->SetCondenseWhiteSpace(false);
		pXML->Parse((const char*)buffer);
		MK_PROCESS_ERROR(pXML->Error() == false);

		node = pXML->RootElement();
		MK_PROCESS_ERROR(node);

		result = strcmp(node->Value(), "Skeleton");
		MK_PROCESS_ERROR(result == 0);

		name = node->Attribute("Name");
		if (name != NULL)
			data.name = name;

		result = node->QueryIntAttribute("BoneNum", &BoneNum);
		MK_PROCESS_ERROR(result == TIXML_SUCCESS);

		result = node->QueryIntAttribute("AnimationFrame", &FrameNum);
		MK_PROCESS_ERROR(result == TIXML_SUCCESS);

		result = node->QueryIntAttribute("Relative", &Relative);
		MK_PROCESS_ERROR(result == TIXML_SUCCESS);

		data.frameCount = (WORD)FrameNum;
		data.relative = Relative;

		node = node->FirstChildElement("Bone");
		for (int i = 0; i < BoneNum; ++i)
		{
			int id = 0;
			int parentIdx = 0;
			const char* name = NULL;
			CSkeletonDataLoader::BoneData* pBoneData = NULL;

			MK_PROCESS_ERROR(node);

			name = node->Attribute("name");
			MK_PROCESS_ERROR(name);

			result = node->QueryIntAttribute("parentIdx", &parentIdx);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pBoneData = new CSkeletonDataLoader::BoneData();
			pBoneData->name = name;
			pBoneData->parentIdx = parentIdx;

			data.vecBoneData.push_back(pBoneData);

			result = __LoadBoneData(pBoneData, node);
			MK_PROCESS_ERROR(result);

			node = node->NextSiblingElement("Bone");
		}

		ret = TRUE;
Exit0:
		SAFE_DELETE(pXML);

		return ret;
	}

	BOOL CSkeletonDataLoader::__LoadBoneData(BoneData* pBoneData, TiXmlElement* pBoneNode)
	{
		BOOL ret = FALSE;
		TiXmlElement* node = NULL;

		node = pBoneNode->FirstChildElement("Translation");
		if (node)
		{
			ret = __LoadTranslationAnimation(pBoneData, node);
			MK_PROCESS_ERROR(ret);
		}

		node = pBoneNode->FirstChildElement("Rotation");
		if (node)
		{
			ret = __LoadRotationAnimation(pBoneData, node);
			MK_PROCESS_ERROR(ret);
		}

		node = pBoneNode->FirstChildElement("Scale");
		if (node)
		{
			ret = __LoadScaleAnimation(pBoneData, node);
			MK_PROCESS_ERROR(ret);
		}

		node = pBoneNode->FirstChildElement("ScaleRotation");
		if (node)
		{
			ret = __LoadScaleRotationAnimation(pBoneData, node);
			MK_PROCESS_ERROR(ret);
		}

		ret = TRUE;

Exit0:
		return ret;
	}

	BOOL CSkeletonDataLoader::__LoadTranslationAnimation(BoneData* pBoneData, TiXmlElement* translationNode)
	{
		BOOL ret = FALSE;
		int result = 0;
		TiXmlElement* node = NULL;
		float x, y, z;

		node = translationNode->FirstChildElement("Frame");

		while (node)
		{
			result = node->QueryFloatAttribute("x", &x);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("y", &y);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("z", &z);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pBoneData->vecAniTranslation.push_back(D3DXVECTOR3(x, y, z));

			node = node->NextSiblingElement("Frame");
		}

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CSkeletonDataLoader::__LoadRotationAnimation(BoneData* pBoneData, TiXmlElement* rotationNode)
	{
		BOOL ret = FALSE;
		int result = 0;
		TiXmlElement* node = NULL;
		float x, y, z, w;

		node = rotationNode->FirstChildElement("Frame");

		while (node)
		{
			result = node->QueryFloatAttribute("x", &x);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("y", &y);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("z", &z);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("w", &w);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pBoneData->vecAniRotation.push_back(D3DXQUATERNION(x, y, z, w));

			node = node->NextSiblingElement("Frame");
		}

		ret = TRUE;

Exit0:
		return ret;
	}

	BOOL CSkeletonDataLoader::__LoadScaleAnimation(BoneData* pBoneData, TiXmlElement* scaleNode)
	{
		BOOL ret = FALSE;
		int result = 0;
		TiXmlElement* node = NULL;
		float x, y, z;

		node = scaleNode->FirstChildElement("Frame");

		while (node)
		{
			result = node->QueryFloatAttribute("x", &x);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("y", &y);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("z", &z);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pBoneData->vecAniScale.push_back(D3DXVECTOR3(x, y, z));

			node = node->NextSiblingElement("Frame");
		}

		ret = TRUE;

Exit0:
		return ret;
	}

	BOOL CSkeletonDataLoader::__LoadScaleRotationAnimation(BoneData* pBoneData, TiXmlElement* scaleRotationNode)
	{
		BOOL ret = FALSE;
		int result = 0;
		TiXmlElement* node = NULL;
		float x, y, z, w;

		node = scaleRotationNode->FirstChildElement("Frame");
		MK_PROCESS_ERROR(node);

		while (node)
		{
			result = node->QueryFloatAttribute("x", &x);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("y", &y);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("z", &z);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("w", &w);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pBoneData->vecAniScaleRotation.push_back(D3DXQUATERNION(x, y, z, w));

			node = node->NextSiblingElement("Frame");
		}

		ret = TRUE;

Exit0:
		return ret;
	}

	BOOL CSkeletonDataLoader::SaveToFile(const char* fileName, CSkeletonDataLoader::SkeletonData& skeletonData)
	{
		if (skeletonData.name.length() > 30)
		{
			std::cout << "skeleton name is invalid" << std::endl;
			return FALSE;
		}

		if (skeletonData.frameCount == 0)
		{
			std::cout << "skeleton frame count is zero" << std::endl;
			return FALSE;
		}

		if (skeletonData.vecBoneData.size() == 0)
		{
			std::cout << "skeleton bone count is zero" << std::endl;
			return FALSE;
		}

		std::vector<BoneData*>::iterator it = skeletonData.vecBoneData.begin();
		while (it != skeletonData.vecBoneData.end())
		{
			BoneData* boneData = *it;
			if (boneData->name.length() > 31)
			{
				std::cout << "bone name is too long [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			if (boneData->parentIdx != -1 && boneData->parentIdx >= (int)skeletonData.vecBoneData.size())
			{
				std::cout << "parent idx is invalid [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			if (boneData->vecAniTranslation.size() != 1 && boneData->vecAniTranslation.size() != skeletonData.frameCount)
			{
				std::cout << "translation animation frame is invalid [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			if (boneData->vecAniRotation.size() != 1 && boneData->vecAniRotation.size() != skeletonData.frameCount)
			{
				std::cout << "rotation animation frame is invalid [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			if (boneData->vecAniScale.size() != 1 && boneData->vecAniScale.size() != skeletonData.frameCount)
			{
				std::cout << "scale animation frame is invalid [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			if (boneData->vecAniScaleRotation.size() != 1 && boneData->vecAniScaleRotation.size() != skeletonData.frameCount)
			{
				std::cout << "scale-rotation animation frame is invalid [" << boneData->name << "]" << std::endl;
				return FALSE;
			}

			++it;
		}

		FILE* output = fopen(fileName, "wb");
		if (!output)
			return FALSE;

		CSkeletonData::Header header;
		memset(&header, 0, sizeof(header));
		header.id = CSkeletonData::MK_SKELETON_ID;
		header.version = CSkeletonData::MK_SKELETON_VER;
		strcpy(header.name, skeletonData.name.c_str());
		header.relative = skeletonData.relative;
		header.frameNum = skeletonData.frameCount;
		header.boneNum = (WORD)skeletonData.vecBoneData.size();
		header.offsetBoneData = sizeof(CSkeletonData::Header);

		// bone data block.
		int boneDataBlockPos = sizeof(CSkeletonData::Header);
		int aniDataBlockPos = boneDataBlockPos + sizeof(CSkeletonData::BoneHeader) * header.boneNum;
		for (int i = 0; i < (int)skeletonData.vecBoneData.size(); ++i)
		{
			BoneData* boneData = skeletonData.vecBoneData[i];
			CSkeletonData::BoneHeader boneHeader;
			memset(&boneHeader, 0, sizeof(boneHeader));
			strcpy(boneHeader.name, boneData->name.c_str());
			boneHeader.parentIdx = boneData->parentIdx;
			boneHeader.translationNum = boneData->vecAniTranslation.size();
			boneHeader.rotationNum = boneData->vecAniRotation.size();
			boneHeader.scaleNum = boneData->vecAniScale.size();
			boneHeader.scaleRotationNum = boneData->vecAniScaleRotation.size();

			// write bone animation data.
			fseek(output, aniDataBlockPos, SEEK_SET);

			boneHeader.offsetTranslation = (DWORD)ftell(output);
			fwrite(&boneData->vecAniTranslation[0], sizeof(D3DXVECTOR3), boneData->vecAniTranslation.size(), output);

			boneHeader.offsetRotation = (DWORD)ftell(output);
			fwrite(&boneData->vecAniRotation[0], sizeof(D3DXQUATERNION), boneData->vecAniRotation.size(), output);

			boneHeader.offsetScale = (DWORD)ftell(output);
			fwrite(&boneData->vecAniScale[0], sizeof(D3DXVECTOR3), boneData->vecAniScale.size(), output);

			boneHeader.offsetScaleRotation = (DWORD)ftell(output);
			fwrite(&boneData->vecAniScaleRotation[0], sizeof(D3DXQUATERNION), boneData->vecAniScaleRotation.size(), output);

			aniDataBlockPos = ftell(output);

			// write bone header.
			fseek(output, boneDataBlockPos + sizeof(CSkeletonData::BoneHeader) * i, SEEK_SET);
			fwrite(&boneHeader, sizeof(boneHeader), 1, output);
		}

		fseek(output, 0, SEEK_SET);
		fwrite(&header, sizeof(header), 1, output);

		fclose(output);
		return TRUE;
	}
}